import { PrismaClient } from "@prisma/client";
import * as fs from "fs";
import * as path from "path";

const prisma = new PrismaClient();

/**
 * 数据库备份工具
 * 将所有表的数据导出为JSON文件，用于迁移前的备份操作
 */
class DatabaseBackup {
  private outputDir: string;

  constructor(outputDir: string = "./backup") {
    this.outputDir = outputDir;
  }

  /**
   * 执行完整的数据库备份
   */
  async backupDatabase(): Promise<void> {
    try {
      console.log("🚀 开始数据库备份...");

      // 创建备份目录
      await this.createBackupDirectory();

      // 获取所有表名
      const tables = await this.getAllTableNames();
      console.log(`📋 发现 ${tables.length} 个表需要备份: ${tables.join(', ')}`);

      let successCount = 0;
      let failCount = 0;

      // 逐个表进行备份
      for (const tableName of tables) {
        try {
          console.log(`🔄 正在备份表: ${tableName}`);
          
          // 获取表数据
          const data = await this.getTableData(tableName);
          
          if (data && data.length > 0) {
            // 保存为JSON文件
            await this.saveToFile(tableName, data);
            console.log(`✅ 表 ${tableName} 备份成功 (${data.length} 条记录)`);
            successCount++;
          } else {
            console.log(`ℹ️  表 ${tableName} 无数据，跳过备份`);
            successCount++;
          }
        } catch (error) {
          console.error(`❌ 表 ${tableName} 备份失败:`, error);
          failCount++;
        }
      }

      console.log(`📊 备份任务完成 - 成功: ${successCount}, 失败: ${failCount}`);
      
      if (failCount > 0) {
        throw new Error(`备份过程中有 ${failCount} 个表失败`);
      }

      // 生成备份信息文件
      await this.generateBackupInfo(tables, successCount);

    } catch (error) {
      console.error('❌ 数据库备份任务失败:', error);
      throw error;
    }
  }

  /**
   * 创建备份目录
   */
  private async createBackupDirectory(): Promise<void> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
    this.outputDir = path.join(this.outputDir, `backup-${timestamp}`);
    
    if (!fs.existsSync(this.outputDir)) {
      fs.mkdirSync(this.outputDir, { recursive: true });
      console.log(`📁 创建备份目录: ${this.outputDir}`);
    }
  }

  /**
   * 获取数据库中所有表名
   */
  private async getAllTableNames(): Promise<string[]> {
    try {
      // 使用 Prisma 的 $queryRaw 查询数据库元数据
      const result = await prisma.$queryRaw<Array<{ tablename: string }>>`
        SELECT tablename 
        FROM pg_tables 
        WHERE schemaname = 'public' 
        AND tablename NOT LIKE '_prisma_%'
        ORDER BY tablename;
      `;
      
      const tableNames = result.map(row => row.tablename);
      
      // 将数据库表名转换为 Prisma 模型名（驼峰命名）
      const prismaModelNames = tableNames.map(tableName => {
        // 将 snake_case 转换为 camelCase
        return tableName.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
      });
      
      // 过滤出在 Prisma Client 中实际存在的模型
      const validModels = prismaModelNames.filter(modelName => {
        try {
          // 检查模型是否存在于 Prisma Client 中
          return (prisma as any)[modelName] !== undefined;
        } catch {
          return false;
        }
      });
      
      console.log(`🔍 数据库表名: ${tableNames.join(', ')}`);
      console.log(`🔍 Prisma模型名: ${prismaModelNames.join(', ')}`);
      console.log(`✅ 有效的Prisma模型: ${validModels.join(', ')}`);
      
      return validModels;
    } catch (error) {
      console.error('获取表名失败:', error);
      throw error;
    }
  }

  /**
   * 获取指定表的所有数据
   */
  private async getTableData(tableName: string): Promise<any[]> {
    try {
      // 使用动态属性访问Prisma模型
      const model = (prisma as any)[tableName];
      if (!model) {
        throw new Error(`表 ${tableName} 不存在`);
      }

      // 获取所有数据
      const data = await model.findMany();
      return data;
    } catch (error) {
      console.error(`获取表 ${tableName} 数据失败:`, error);
      throw error;
    }
  }

  /**
   * 将数据保存为JSON文件
   */
  private async saveToFile(tableName: string, data: any[]): Promise<void> {
    try {
      // 生成文件名
      const fileName = `${tableName}.json`;
      const filePath = path.join(this.outputDir, fileName);

      // 将数据序列化为JSON
      const jsonData = JSON.stringify(data, null, 2);
      
      // 写入文件
      fs.writeFileSync(filePath, jsonData, 'utf8');
      
      console.log(`📄 文件已保存: ${filePath}`);
      
    } catch (error) {
      console.error(`保存表 ${tableName} 到文件失败:`, error);
      throw error;
    }
  }

  /**
   * 生成备份信息文件
   */
  private async generateBackupInfo(tables: string[], successCount: number): Promise<void> {
    try {
      const backupInfo = {
        backupTime: new Date().toISOString(),
        databaseUrl: process.env.DATABASE_URL?.replace(/\/\/.*@/, '//***:***@') || 'N/A', // 隐藏敏感信息
        totalTables: tables.length,
        successCount,
        tables: tables.map(table => ({
          name: table,
          fileName: `${table}.json`,
          status: 'success'
        })),
        notes: [
          '此备份文件用于数据库迁移前的数据备份',
          '可以使用 restore-database.ts 脚本恢复数据',
          '备份文件包含所有表的数据，以JSON格式存储'
        ]
      };

      const infoPath = path.join(this.outputDir, 'backup-info.json');
      fs.writeFileSync(infoPath, JSON.stringify(backupInfo, null, 2), 'utf8');
      
      console.log(`📋 备份信息已保存: ${infoPath}`);
      
    } catch (error) {
      console.error('生成备份信息失败:', error);
    }
  }

  /**
   * 获取备份目录路径
   */
  getBackupDirectory(): string {
    return this.outputDir;
  }
}

/**
 * 主函数
 */
async function main() {
  const backup = new DatabaseBackup();
  
  try {
    await backup.backupDatabase();
    
    console.log('\n🎉 数据库备份完成！');
    console.log(`📁 备份文件位置: ${backup.getBackupDirectory()}`);
    console.log('\n📋 备份文件列表:');
    
    // 列出备份文件
    const files = fs.readdirSync(backup.getBackupDirectory());
    files.forEach(file => {
      const filePath = path.join(backup.getBackupDirectory(), file);
      const stats = fs.statSync(filePath);
      console.log(`  - ${file} (${(stats.size / 1024).toFixed(2)} KB)`);
    });
    
  } catch (error) {
    console.error('备份失败:', error);
    process.exit(1);
  } finally {
    await prisma.$disconnect();
  }
}

// 如果直接运行此脚本
if (require.main === module) {
  main();
}

export default DatabaseBackup;
